/*
 * (C) 2009, Elephant Seven Hamburg Gmbh, 
 * Author: Detlev Wiese
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 
 * use this file except in compliance with the License. You may obtain a copy 
 * of the License at
 *  
 *     http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.se and its accompanying notice file is
 * included with the distribution. 
 */ 

package com.e7.streams;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;

/**
 * This class provides helper methods, which may be needed in 
 * stream-oriented applications. 
 */
public class Streams {

	// Deny instantiation.
	private Streams() {}
	
	/**
	 * <P>Read the given Stream to its end. No values are stored or 
	 * accessed here.</P>
	 * <P>Do not use this method with infinite streams! It will not 
	 * return.</P>
	 * @param s The stream instance to read
	 */
	public static void drain(Stream<?> s) { 
		while (s.hasNext()) s.next();
	}
	
	/**
	 * <P>Read the given Stream to its end and put all its Elements 
	 * into the collection given.</P>
	 * <P>Do not use this method with infinite streams! It will not 
	 * return.</P>
	 * @param E The type of the stream elements
	 * @param s The stream instance to read
	 * @param c The collection to fill with the elements of the 
	 * stream
	 */
	public static <E> void drainInto(Stream<? extends E> s, Collection<E> c) 
	{
		for (E e: s) c.add(e);
	}
	
	
	/**
	 * Return a new sorted Stream based on the elements of the stream 
	 * given. 
	 * @param <E> The Type of the stream elements 
	 * @param stream The stream to sort
	 * @return A new stream object
	 */
	public static <E extends Comparable<? super E>> Stream<E> 
		sortStream(Stream<E> stream) 
	{ 
		List<E> list = new ArrayList<E>();
		drainInto(stream, list);
		Collections.sort(list);
		return new CollectionStream<E>(list);
	}
	

	/**
	 * Return a sorted stream based on the elements of the stream 
	 * given. The elements will be sorted using the {@link Comparator}. 
	 * @param <E> The type of the elements of the stream to sort
	 * @param stream The stream to sort
	 * @param comparator The comparator to use in sort. You may pass 
	 * {@code null} here, which means, the sorting should use the 
	 * natural order of the elements (underlying functionality is
	 * provided by {@link Collections#sort(List, Comparator)}).
	 * @return A new stream object backed by a {@code List}.
	 */
	public static <E> Stream<E> sortStream(
		Stream<E> stream, 
		Comparator<? super E> comparator) 
	{ 
		List<E> list = new ArrayList<E>();
		drainInto(stream, list);
		Collections.sort(list, comparator);
		return new CollectionStream<E>(list);
	}
	

	/**
	 * An empty stream. Type-safe.
	 * @param <E> The element type of the empty stream..
	 * @return always the same empty stream object 
	 */
	public static <E> Stream<E> emptyStream() {
		return EmptyStream.soleInstance();
	}
	
	/**
	 * Return a stream which is the reversal of the stream
	 * given.
	 * @param <E> The element type of the stream to reverse
	 * @return The newly created stream which is a reversal of 
	 * the stream given backed by a {@code List}!
	 */
	public static <E> Stream<E> reverseStream(Stream<E> stream) {
		LinkedList<E> list = new LinkedList<E>();
		for(E e : stream) 
			list.addFirst(e);
		return new CollectionStream<E>(list);
	}
	
	/**
	 * Return the minimum out of the the elements of the stream given.
	 * @param <E> The type of the elements
	 * @param stream The stream
	 * @param comparator The comparator to use
	 * @return {@code null} for empty streams, the minimum element otherwise. 
	 * If more than on element is minimal, the first element will be delivered.
	 */
	public static <E> E min(Stream<E> stream, Comparator<? super E> comparator) 
	{
		E result = null; 
		if (stream.hasNext()) {
			result = stream.next();
			for (E element : stream) 
				if (comparator.compare(element, result) < 0) 
					result = element; 
		}
		return result;
	}
	
	/**
	 * Return the minimum out of the the elements of the 
	 * stream given.
	 * @param <E> The type of the elements
	 * @param stream The stream
	 * @return {@code null} for empty streams, the minimum element otherwise. 
	 * If more than on element is minimal, the first element will be delivered.
	 */
	public static <E extends Comparable<? super E>> E min(Stream<E> stream) 
	{
		return min(stream, new Comparator<E>() {
			public int compare(E e1, E e2) {
				return e1.compareTo(e2); // parameter order for min!
			}
		});
	}
	
	/**
	 * Return the maximum out of the the elements of the stream given.
	 * @param <E> The type of the elements
	 * @param stream The stream
	 * @param comparator The comparator to use
	 * @return {@code null} for empty streams, the maximum element otherwise. 
	 * If more than on element is maximal, the first element will be delivered.
	 */
	public static <E> E max(Stream<E> stream, final Comparator<? super E> comparator) 
	{
		return min(stream, new Comparator<E>() {
			public int compare(E e1, E e2) {
				return comparator.compare(e2, e1); // parameter order for max!
			}
		});
	}
	
	/**
	 * Return the maximum out of the the elements of the stream given.
	 * @param <E> The type of the elements
	 * @param stream The stream
	 * @return {@code null} for empty streams, the maximum element otherwise. 
	 * If more than on element is maximal, the first element will be delivered.
	 */
	public static <E extends Comparable<? super E>> E max(Stream<E> stream) 
	{
		return min(stream, new Comparator<E>() {
			public int compare(E e1, E e2) {
				return e2.compareTo(e1); // parameter order for max!
			}
		});
	}
	
	/**
	 * <P>Return the last element of the stream given!</P>
	 * <P>Do not use this method with infinite streams! It will 
	 * not return.</P>
	 * <P>If there are no elements present, this method will throw a 
	 * {@link java.util.NoSuchElementException}.</P>
	 * @param <E> The type of the elements of the stream
	 * @param stream The stream to read up to the last element
	 * @return The last element of the stream
	 */
	public static <E> E last(Stream<E> stream) {
		if (!stream.hasNext()) 
			throw new NoSuchElementException();
		
		E element = null; 
		while (stream.hasNext()) 
			element = stream.next();
		return element; 
	}		
	
	/**
	 * <P>Return the number of elements of the stream given!</P>
	 * <P>Do not use this method with infinite streams! It will not 
	 * return.</P>
	 * @param stream The stream to read up to the last element
	 * @return The count of elements of the stream
	 */
	public static int count(Stream<?> stream) {
		int c = 0;
		while (stream.hasNext()) { 
			stream.next();
			c++;
		}
		return c; 
	}		
	
	/**
	 * <P>Return the stream given as an {@link Enumeration}-instance.</P>
	 * <P>The enumeration returned will depend on the original stream 
	 * object, so if the enumeration is read by some consumer, the stream 
	 * will also advance at the same time.</P> 
	 * @param <E> The element type of the stream parameter
	 * @param stream The stream to return as an enumeration
	 * @return a {@link java.util.Enumeration}-instance
	 * @throws IllegalArgumentException in case of a <code>null</code> parameter
	 */
	public static <E> Enumeration<E> asEnumeration(final Stream<E>  stream) {
		if (null == stream)
			throw new IllegalArgumentException();
		
		if (stream instanceof AbstractStream<?>) 
			return (AbstractStream<E>) stream;
		else {
			return new Enumeration<E>() {
				public boolean hasMoreElements() {
					return stream.hasNext();
				}
	
				public E nextElement() {
					return stream.next();
				}
			};
		}
	}
	
	/**
	 * <P>Return a Stream for the {@link Iterator}-instance given.</P>
	 * @param <E> The type of the {@link Iterator} elements.
	 * @param iterator The iterator to return as Stream.
	 * @return a Stream based on the iterator state
	 * @throws IllegalArgumentException in case of a <code>null</code> 
	 * parameter
	 */
	public static <E> Stream<E> asStream(Iterator<E> iterator) {
		if (null == iterator)
			throw new IllegalArgumentException();
		
		return new CollectionStream<E>(iterator);
	}
	
	
	/**
	 * <P>Return the n-th element of the stream given; if no such element 
	 * exists, a {@link NoSuchElementException} will be thrown.</P>
	 * <P>This method will return <code>null</code> in case of n = 0.</P>
	 * @param <E> The type of the elements
	 * @param n the n in 'n-th'
	 * @param stream the stream to read n times. 
	 * @return the element read with after n successful reads. 
	 * @throws NoSuchElementException
	 */
	public static <E> E nth(int n, Stream<E> stream) {
		E result = null ; 
		while (n > 0) { 
			n--; 
			result = stream.next();
		}
		return result; 
	}
}
